#pragma once

#include "Common.hpp"
#include "PageMap.hpp"

#ifdef _WIN64
	#define USING_TCMALLOC_PAGEMAP3 30     // 注意：该表只能用于 32 / 64 位
#else
	//#define USING_TCMALLOC_PAGEMAP3 30     // 注意：该表只能用于 32 / 64 位
	#define USING_TCMALLOC_PAGEMAP1 10     // 注意：该表只能用于 32 位
	//#define USING_TCMALLOC_PAGEMAP2 20     // 注意：该表只能用于 32 位
#endif // _WIN32

//#define USING_TCMALLOC_PAGEMAP1 10     // 注意：该表只能用于 32 位
//#define USING_TCMALLOC_PAGEMAP2 20     // 注意：该表只能用于 32 位
//#define USING_TCMALLOC_PAGEMAP3 30     // 注意：该表只能用于 32 / 64 位
//#define USING_UNORDERED_MAP 40         // 注意：该表只能用于 32 / 64 位



//  当前

class PageCache:protected PtrHelper,protected AllocHelper
{
	PageCache(const PageCache&) = delete;
	PageCache operator=(const PageCache&) = delete;
	PageCache(){}
private:
	static PageCache _pagecache;
	SpanList _spanList[NPAGES];

	// 定长内存池 用于 new Span
	ObjectPool<Span> _spanpool;
	

	// 页号和 Span* 的映射关系
#ifdef USING_TCMALLOC_PAGEMAP1
	TCMalloc_PageMap1<Span, 32 - PAGE_SHIFT > _idSpanMap;  // 默认存放指针类型
#elif USING_TCMALLOC_PAGEMAP2
	TCMalloc_PageMap2<Span, 32 - PAGE_SHIFT > _idSpanMap;  // 默认存放指针类型
#elif USING_TCMALLOC_PAGEMAP3

#ifdef _WIN64
	TCMalloc_PageMap3<Span, 64 - PAGE_SHIFT > _idSpanMap;  // 默认存放指针类型
#else
	TCMalloc_PageMap3<Span, 32 - PAGE_SHIFT > _idSpanMap;  // 默认存放指针类型
#endif // _WIN32
	
#elif USING_UNORDERED_MAP
	std::unordered_map<PAGE_ID, Span*> _idSpanMap;  
#endif // USING_TCMALLOC_PAGEMAP1

	
	
public:
	std::mutex _mutex;
public:
	static PageCache& GetInstanse()
	{
		return _pagecache;
	}

	Span* MapObjectToSpan(void* obj)
	{
#ifdef USING_TCMALLOC_PAGEMAP1
		
#elif USING_UNORDERED_MAP
		std::unique_lock<std::mutex> lock(_mutex);  // 读取时存在线程安全问题
#endif // USING_TCMALLOC_PAGEMAP1
		   
		// 将地址 /8k,得到对应的页号
		PAGE_ID id = (PAGE_ID)obj >> PAGE_SHIFT;
		auto ret = _idSpanMap.find(id);
		if (ret != _idSpanMap.end())
		{
			// 存在
			return ret->second;
		}
		else
		{
			// 这个一定能够映射到，如果映射不到，就有逻辑错误
			assert(false);
			return nullptr;
		}
	}



	// 获取 k 页的Span  (调用这个函数时，外部是加锁的)
	Span* newSpan(size_t k)   // 大小为 k page 的桶
	{
		assert(k > 0);

		if (k > NPAGES - 1)  // 大于 128 页，即 1M 的，直接向堆获取
		{
			void* ptr = SystemAlloc(k);
			//Span* span = new Span;
			Span* span = _spanpool.New();
			span->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;
			span->_n = k;
			_idSpanMap[span->_pageId] = span;
			return span;
		}

		// 小于 128 页的， 1MB 以内的，都在桶中

		if (!_spanList[k].Empty())
		{
			// 获取大小为 k page 的桶中的一个 Span
			Span* span = _spanList[k].PopFront();

			for (PAGE_ID i = 0; i < span->_n; i++)    // 这里也需要映射
			{
				_idSpanMap[span->_pageId + i] = span;
			}

			return span;
		}

		// 遍历后面的大内存桶，寻找可以切分的桶
		for (size_t i = k + 1; i < NPAGES; i++)
		{
			if (!_spanList[i].Empty())
			{
				// 不空
				Span* nspan = _spanList[i].PopFront();
				
				//Span* kspan = new Span;
				Span* kspan = _spanpool.New();

				// 采取头切的方式，将大块内存的头部切下来分配给kspan
				kspan->_pageId = nspan->_pageId;
				kspan->_n = k;

				// 切分后 原大块内存的id  += k
				//_idSpanMap[kspan->_pageId + k - 1] = kspan;

				nspan->_pageId += k; 
				nspan->_n -= k;

				//_idSpanMap[nspan->_pageId] = nspan;
				
				// 将切分剩下的防区 n-k 的桶中·
				_spanList[nspan->_n].PushFront(nspan);

				// 这里只需要为分配给 CentralCache 的内存做 Hash映射即可
				// 未分配除去的不需要映射
				for (PAGE_ID i = 0; i < kspan->_n; i++)
				{
					// 如果一次性分区出多页，那么多页都映射为同一个 kspan
					// 原因：假设分出去 2k 大小，这 2k个 span 会在 CentralCache 中切分成 byte_size 大小的内存块
					//       在使用过程中，可以一部分（1，5，8，2，100）这些【序号】的内存块被返回，此时是乱序的
					//       但他们同属于这个 2k 的 Span ,此时，根据这些零碎的内存块，映射到的应该都是这个 2k 的Span的地址
					
					// ①这里对 kspan的映射是给 CentealCache 用的,用于后续回收的时候映射
					_idSpanMap[kspan->_pageId + i] = kspan;
				}

				// ②这里对 nspan 的首位两页进行映射，是为了在 PageCache 合并相邻两页的内存时方便寻找
				_idSpanMap[nspan->_pageId] =  nspan;
				_idSpanMap[nspan->_pageId + nspan->_n - 1] = nspan;



				return kspan;
			}
		}
		// 走到这里说明没有大页 span 了，重新 向堆申请一个 128 页的 span
		
		//Span* bigspan = new Span;
		Span* bigspan = _spanpool.New();

		void* ptr = SystemAlloc(NPAGES - 1);  // 128 page * 8k 
		bigspan->_pageId = (PAGE_ID)ptr >> PAGE_SHIFT;  //  ptr / 8K
		bigspan->_n = NPAGES - 1;
		// 挂载到最后一页
		_spanList[NPAGES - 1].PushFront(bigspan);

		return newSpan(k);
	}




	// 将分配出去的 Span 归还给 PageCahce
	void ReleaseSpanToPageCache(Span* span)
	{
		if (span->_n > NPAGES - 1)  // 大于 128 页不再 SpanList 内
		{
			//  大于 1MB, 直接向系统申请的，这里也直接释放
			SystemFree((void*)(span->_pageId << PAGE_SHIFT));
			return;
		}

		// contral : true -> +1(向后合并)  false -> -1(向前合并)
		// 1. 向前合并
		mergeSpan(span, false);
		// 2. 向后合并
		mergeSpan(span, true);
		
		// 将合并后的整块内存放入新的位置
		_spanList[span->_n].PushFront(span);
		// 将新的span 的前后两端重新映射
		_idSpanMap[span->_pageId] = span;
		_idSpanMap[ span->_pageId + span->_n - 1] = span;
		
		span->_isUsed = false;  // 修改为未使用
	}



private:
	void mergeSpan(Span* span,bool contral)
	{
		while (1)
		{
			// contral : true -> +1(向后合并)  false -> -1(向前合并)
			//auto it = _idSpanMap.find(contral ? span->_pageId + 1 : span->_pageId - 1);  // 向后合并 + span->_n
			auto it = _idSpanMap.find(contral ? span->_pageId + span->_n : span->_pageId - 1);
		
			//   没有相邻的页              相邻的页正在使用             合并后会大于当前 _spanList 所能映射的最大页
			if (it == _idSpanMap.end() || it-> second->_isUsed == true || it->second->_n + span->_n > NPAGES - 1)
			{
				break;
			}
			Span* _nearSpan = it->second;
			span->_n += _nearSpan->_n;
			
			// contral : true -> (向后合并)  false -> (向前合并)
			span->_pageId = contral ? span->_pageId : _nearSpan->_pageId;
			
			// 将span从对应大小的 _soanList 中删除
			_spanList[_nearSpan->_n].Erase(_nearSpan);

			// 可能不需要
			//// 删除之前的映射关系
			//for (size_t i = 0; i < _nearSpan->_n; i++)
			//{
			//	_idSpanMap.erase(_nearSpan->_pageId + i);
			//}

			////delete _nearSpan;

			////  删除之前的映射关系
			//_idSpanMap.erase(_nearSpan->_pageId);
			//_idSpanMap.erase(_nearSpan->_pageId + _nearSpan->_n - 1);

			_spanpool.Delete(_nearSpan);
		}
	}
};

// 静态变量需要先声明
PageCache PageCache::_pagecache;